<?php

namespace Wgx\Admin\Com;

use Wgx\Admin\Entity\AdminUserEntity;
use PhpYes\Com\Base64UrlSafeEncoder;
use PhpYes\Com\RedisManager;
use PhpYes\Com\WebSessionInterface;

class AdminSession implements WebSessionInterface
{
    /** @var null|AdminSession */
    private static $singleObj = null;

    /**
     * 存放 session 的 redis
     * @var null|\Redis
     */
    private $redis = null;
    private $sessionKey = 'sid';
    private $sessionIdPrefix = null;
    private $sessionId = null;
    private $ttl = 86400; // 1 day = 24 * 60 * 60 second
    private $sessionDomain = null;
    private $session = null;

    /** @var null|AdminUserEntity */
    private $adminUserEntity = null;

    public static function init()
    {
        $redis = RedisManager::getSingleObj()->getRedis('redisDb');
        self::$singleObj = new AdminSession($redis, 'adminsid', 'admin', 7200);
        self::$singleObj->initSession();
    }

    /**
     * @return AdminSession|null
     */
    public static function getSingleObj()
    {
        return self::$singleObj;
    }

    /**
     * AdminSession constructor.
     * @param \Redis $redis - 存放 session 的 redis
     * @param string $sessionKey - session id 在所有 cookie 中的 key
     * @param null|string $sessionIdPrefix - session id 的前缀
     * @param int $ttl - session 存活的时间
     * @param null|string $sessionDomain - session的cookie种到的域，默认是种到当前的域名下，不过有些情况下需要将session的cookie种到特定的域下，例如跨域开发时
     */
    private function __construct($redis, $sessionKey = 'sid', $sessionIdPrefix = null, $ttl = 86400, $sessionDomain = null)
    {
        $this->redis = $redis;
        $this->sessionKey = $sessionKey;
        $this->sessionIdPrefix = $sessionIdPrefix;
        $this->ttl = $ttl;
        $this->sessionDomain = $sessionDomain;
    }

    private function initSession()
    {
        $sKey = empty($this->sessionKey) ? null : trim($this->sessionKey);
        if (empty($sKey)) {
            return;
        }
        $sid = empty($_COOKIE[$sKey]) ? null : trim($_COOKIE[$sKey]);
        $session = $this->redis->get($sid);
        if ($session) {
            $session = trim($session);
            if ($session) {
                $session = json_decode($session, true);
            }
        }
        $nowTs = time();
        $ttl = empty($this->ttl) ? 0 : intval($this->ttl);
        if ($ttl < 1) {
            $ttl = 1;
        }
        if (empty($sid) || empty($session)) {
            $sid = $this->getSessionId();
            $session = array(
                'createTs' => $nowTs,
                'updateTtlTs' => $nowTs
            );
            setcookie($sKey, $sid, $nowTs + $ttl, '/', empty($this->sessionDomain) ? null : trim($this->sessionDomain));
            $this->redis->setex($sid, $ttl, json_encode($session));
            $this->session = $session;
        } else {
            $this->sessionId = $sid;
            $this->session = $session;
            $lastUpdateTtlTs = empty($session['updateTtlTs']) ? 0 : intval($session['updateTtlTs']);
            if ($lastUpdateTtlTs < $nowTs - $ttl / 2) {
                $session['updateTtlTs'] = $nowTs;
                setcookie($sKey, $sid, $nowTs + $ttl, '/', empty($this->sessionDomain) ? null : trim($this->sessionDomain));
                $this->redis->expire($sid, $ttl);
                $this->session = $session;
            }
        }
    }

    /**
     * @return \Redis|null
     */
    public function getRedis()
    {
        return $this->redis;
    }

    /**
     * 将管理员和 session 绑定起来
     * @param $adminId
     */
    public function bindAdmin($adminId)
    {
        $adminId = intval($adminId);
        $this->put('adminId', $adminId);
    }

    /**
     * 将管理员和 session 解除绑定
     */
    public function unbindAdmin()
    {
        $this->put('adminId', 0);
    }

    /**
     * @return int
     */
    public function getAdminId()
    {
        return empty($this->session['adminId']) ? 0 : intval($this->session['adminId']);
    }

    /**
     * @return AdminUserEntity|null
     */
    public function getAdminUserEntity()
    {
        if (empty($this->adminUserEntity)) {
            $adminId = $this->getAdminId();
            if ($adminId) {
                $this->adminUserEntity = AdminUserEntity::getSingle()->selectById($adminId);
            }
        }
        return $this->adminUserEntity;
    }

    // implements WebSessionInterface ...

    /**
     * @inheritDoc
     */
    public function put($key, $val)
    {
        $key = empty($key) ? null : trim($key);
        if (empty($key) || empty($this->session)) {
            return;
        }

        if (isset($val)) {
            $this->session[$key] = $val;
        } else {
            if (isset($this->session[$key])) {
                unset($this->session[$key]);
            } else {
                return;
            }
        }

        $sid = $this->getSessionId();

        $lastUpdateTtlTs = empty($this->session['updateTtlTs']) ? 0 : intval($this->session['updateTtlTs']);
        $ttl = empty($this->ttl) ? 0 : intval($this->ttl);
        if ($ttl < 1) {
            $ttl = 1;
        }
        $ttlLeft = $lastUpdateTtlTs + $ttl - time();
        if ($ttlLeft < 1) {
            $ttlLeft = 1;
        }

        $this->redis->setex($sid, $ttlLeft, json_encode($this->session));
    }

    /**
     * @inheritDoc
     */
    public function get($key)
    {
        $key = empty($key) ? null : trim($key);
        if (empty($key)) {
            return null;
        }
        return empty($this->session[$key]) ? null : $this->session[$key];
    }

    /**
     * @inheritDoc
     */
    public function delete($key)
    {
        $key = empty($key) ? null : trim($key);
        if (empty($key)) {
            return;
        }
        $this->put($key, null);
    }

    /**
     * @inheritDoc
     */
    public function putex($key, $val, $ttl)
    {
        $key = empty($key) ? null : trim($key);
        $val = empty($val) ? null : trim($val);
        $ttl = empty($ttl) ? 0 : intval($ttl);
        if (empty($key) || empty($val) || $ttl < 1) {
            return;
        }

        $sid = $this->getSessionId();
        $this->redis->setex("{$sid}-{$key}", $ttl, $val);
    }

    /**
     * @inheritDoc
     */
    public function getex($key)
    {
        $key = empty($key) ? null : trim($key);
        if (empty($key)) {
            return null;
        }

        $sid = $this->getSessionId();
        return $this->redis->get("{$sid}-{$key}");
    }

    /**
     * @inheritDoc
     */
    public function deleteex($key)
    {
        $key = empty($key) ? null : trim($key);
        if (empty($key)) {
            return 0;
        }

        $sid = $this->getSessionId();
        return $this->redis->del("{$sid}-{$key}");
    }

    /**
     * @inheritDoc
     */
    public function getSessionKey()
    {
        return $this->sessionKey;
    }

    /**
     * @inheritDoc
     */
    public function getSessionIdPrefix()
    {
        return $this->sessionIdPrefix;
    }

    /**
     * @inheritDoc
     */
    public function getSessionId()
    {
        if (empty($this->sessionId)) {
            $prefix = empty($this->sessionIdPrefix) ? null : trim($this->sessionIdPrefix);
            if (empty($prefix)) {
                $this->sessionId = strval(time()) . '-' . Base64UrlSafeEncoder::encode(openssl_random_pseudo_bytes(16));
            } else {
                $this->sessionId = $prefix . '-' . strval(time()) . '-' . Base64UrlSafeEncoder::encode(openssl_random_pseudo_bytes(12));
            }
        }
        return $this->sessionId;
    }

    /**
     * @inheritDoc
     */
    public function getTtl()
    {
        return $this->ttl;
    }

    /**
     * @inheritDoc
     */
    public function getTtlLeft()
    {
        if (empty($this->session)) {
            return 0;
        }

        $lastUpdateTtlTs = empty($this->session['updateTtlTs']) ? 0 : intval($this->session['updateTtlTs']);
        $ttl = empty($this->ttl) ? 0 : intval($this->ttl);
        if ($ttl < 1) {
            $ttl = 1;
        }
        return $lastUpdateTtlTs + $ttl - time();
    }

    /**
     * @inheritDoc
     */
    public function getSessionDomain()
    {
        return $this->sessionDomain;
    }

    /**
     * @inheritDoc
     */
    public function getSession()
    {
        return $this->session;
    }

    /**
     * @inheritDoc
     */
    public function getCreateTs()
    {
        return empty($this->session['createTs']) ? 0 : intval($this->session['createTs']);
    }

    /**
     * @inheritDoc
     */
    public function getUpdateTtlTs()
    {
        return empty($this->session['updateTtlTs']) ? 0 : intval($this->session['updateTtlTs']);
    }
}